﻿using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

using Suplex.Data;
using System.Windows.Forms;
using Suplex.General;

/*
public class SprocketScriptTemplate
{
	public SortedDictionary<string, string> GetVariables(DataSet ds, string fullyQualifiedTableName)
	{
		return new SortedDictionary<string, string>();
	}
}
*/

namespace sprocket
{
	public class ScriptEngine
	{
		private Guid _sessionId = Guid.Empty;
		private Dictionary<string, Assembly> _assemblyCache = null;
		private const string __compilerOptionsBeginTag = "<CompilerOptions>";
		private const string __compilerOptionsEndTag = "</CompilerOptions>";

		public ScriptEngine()
		{
			_sessionId = Guid.NewGuid();
			_assemblyCache = new Dictionary<string, Assembly>();
		}

		~ScriptEngine()
		{
			foreach( string outputAssmembly in _assemblyCache.Keys )
			{
				if( File.Exists( outputAssmembly ) )
				{
					try
					{
						File.Delete( outputAssmembly );
					}
					catch { }
				}
			}
		}

		public void ExecuteSql(Template t, string currentTable, ref DataSet ds, DataAccessor da)
		{
			string buf = t.ScriptTemplate;

			if( t.TemplateType == TemplateType.TableAwareData && !string.IsNullOrEmpty( currentTable ) )
			{
				string[] ts = currentTable.Split( '.' );
				string schemaname = ts[1];
				string tablename = ts[2];

				buf = Regex.Replace( buf, "@TABLE_NAME", tablename );
				buf = Regex.Replace( buf, "@SCHEMA_NAME", schemaname );
			}

			DataSet temp = da.GetDataSet( buf );
			if( temp.Tables.Count > 0 )
			{
				temp.Tables[0].TableName = t.Name;
				if( ds.Tables.Contains( t.Name ) )
				{
					ds.Tables.Remove( t.Name );
				}
				ds.Tables.Add( temp.Tables[0].Copy() );
			}
		}

		public bool ExecuteCSharpCode(Template t, DataSet ds, string currentTable, ref SortedDictionary<string, string> result)
		{
			bool ok = false;
			Assembly assembly = null;

			if( result == null ) { result = new SortedDictionary<string, string>(); }

			string outputAssembly = string.Format( "{0}\\sprocket_{1}_{2}.dll", Application.StartupPath, _sessionId, t.Name );

			if( !File.Exists( outputAssembly ) )
			{
				CompilerOptions options = new CompilerOptions();
				Match m = Regex.Match( t.ScriptTemplate, __compilerOptionsBeginTag );
				if( m.Success )
				{
					int beginOptions = m.Index;

					m = Regex.Match( t.ScriptTemplate, __compilerOptionsEndTag );
					if( m.Success )
					{
						int endOptions = (m.Index + __compilerOptionsEndTag.Length) - beginOptions;
						string optionsText = t.ScriptTemplate.Substring( beginOptions, endOptions );
						options = CompilerOptions.Deserialize( optionsText );
					}
				}

				Dictionary<string, string> providerOptions = new Dictionary<string, string>();
				if( options.HasVersion ) { providerOptions.Add( "CompilerVersion", options.Version ); }

				CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider( providerOptions );
				CompilerParameters cp = new CompilerParameters();
				cp.GenerateExecutable = false;
				cp.OutputAssembly = outputAssembly;
				cp.GenerateInMemory = true;
				cp.TreatWarningsAsErrors = false;
				if( options.HasReferences )
				{
					foreach( string r in options.ReferenceList )
					{
						cp.ReferencedAssemblies.Add( r );
					}
				}

				CompilerResults cr = provider.CompileAssemblyFromSource( cp, t.ScriptTemplate );
				ok = !cr.Errors.HasErrors;

				if( ok )
				{
					assembly = cr.CompiledAssembly;
					_assemblyCache.Add( outputAssembly, cr.CompiledAssembly );
				}
				else
				{
					int e = 0;
					foreach( CompilerError ce in cr.Errors )
					{
						result.Add( e++.ToString(), ce.ToString() );
					}
				}
			}
			else
			{
				//assembly = Assembly.LoadFile( outputAssembly );
				assembly = _assemblyCache[outputAssembly];
				ok = assembly != null;
				if( !ok )
				{
					result.Add( DateTime.Now.Ticks.ToString(),
						string.Format( "Could not load cached assembly {0}", outputAssembly ) );
				}
			}

			if( ok )
			{
				object instance = assembly.CreateInstance( "SprocketScript" );
				try
				{
					object methodResult = instance.GetType().InvokeMember(
						"GetVariables", BindingFlags.InvokeMethod,
						null, instance, new object[] { ds, currentTable } );
					SortedDictionary<string, string> vars = (SortedDictionary<string, string>)methodResult;
					foreach( KeyValuePair<string,string> var in vars )
					{
						result[var.Key] = var.Value;
					}
					ok = true;
				}
				catch( Exception ex )
				{
					result.Add( DateTime.Now.Ticks.ToString(), ex.ToString() );
				}
			}

			return ok;
		}

		public void ExecuteTSqlCode(Template t, string currentTable, ref SortedDictionary<string, string> result, DataAccessor da)
		{
			if( result == null ) { result = new SortedDictionary<string, string>(); }

			string buf = t.ScriptTemplate;

			if( t.TemplateType == TemplateType.TSqlCode && !string.IsNullOrEmpty( currentTable ) )
			{
				string[] ts = currentTable.Split( '.' );
				string schemaname = ts[1];
				string tablename = ts[2];

				buf = Regex.Replace( buf, "@TABLE_NAME", tablename );
				buf = Regex.Replace( buf, "@SCHEMA_NAME", schemaname );
			}

			DataSet ds = da.GetDataSet( buf );
			foreach( DataTable table in ds.Tables )
			{
				if( table.Columns.Contains( "key" ) && table.Columns.Contains( "value" ) )
				{
					foreach( DataRow row in table.Rows )
					{
						result[row["key"].ToString()] = row["value"].ToString();
					}
				}
			}
		}
	}

	public class CompilerOptions
	{
		public string Version { get; set; }
		public string References { get; set; }

		[System.Xml.Serialization.XmlIgnore()]
		public bool HasVersion { get { return !string.IsNullOrEmpty( this.Version ); } }
		[System.Xml.Serialization.XmlIgnore()]
		public string[] ReferenceList { get; private set; }
		[System.Xml.Serialization.XmlIgnore()]
		public bool HasReferences { get { return !string.IsNullOrEmpty( this.References ); } }

		public string Serialize()
		{
			this.References = "x.x;y.y";
			this.Version = "v3.5";
			UTF8Encoding encoding = new UTF8Encoding( false );
			string x = XmlUtils.Serialize<CompilerOptions>( this, true, true, false, encoding );
			return x;
		}

		public static CompilerOptions Deserialize(string data)
		{
			StringReader r = new StringReader( data );
			CompilerOptions c = XmlUtils.Deserialize<CompilerOptions>( r );
			c.ReferenceList = c.References.Split( ';' );
			return c;
		}
	}
}